package main

import (
	"embed"
	"flag"
	"fmt"
	"io/fs"
	"iptables-admin/common"
	"iptables-admin/router"
	"iptables-admin/utils"
	"net/http"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
)

//go:embed web/dist
var embedFrontend embed.FS

var (
	username   string // IPT_WEB_USERNAME
	password   string // IPT_WEB_PASSWORD
	port       string // IPT_WEB_PORT
	verifyArgs *regexp.Regexp
)

func init() {
	flag.StringVar(&username, "u", "admin", "login username")
	flag.StringVar(&password, "p", "admin", "login password")
	flag.StringVar(&port, "a", "9999", "http listen on")
	flag.Parse()
	if v := os.Getenv("IPT_WEB_USERNAME"); len(v) > 0 {
		username = v
	}
	if v := os.Getenv("IPT_WEB_PASSWORD"); len(v) > 0 {
		password = v
	}
	if v := os.Getenv("IPT_WEB_PORT"); len(v) > 0 {
		port = v
	}
	// 验证表名链名，防止注入
	verifyArgs, _ = regexp.Compile(`^[0-9A-z-_]+$`)
}

func main() {
	r := gin.Default()
	initRouter(r)
	r.Use(Cors())
	r.Use(argsFilter())

	router := router.NewRouter()
	noauth := r.Group("../api2")
	noauth.POST("/login", login)

	auth := r.Group("../api", auth())
	auth.GET("/version", router.Version)
	auth.POST("/listRule", router.ListRule)
	auth.POST("/listExec", router.ListExec)
	auth.POST("/flushRule", router.FlushRule)
	auth.POST("/deleteRule", router.DeleteRule)
	auth.POST("/flushMetrics", router.FlushMetrics)
	auth.POST("/getRuleInfo", router.GetRuleInfo)
	auth.POST("/flushEmptyCustomChain", router.FlushEmptyCustomChain)
	auth.POST("/export", router.Export)
	auth.POST("/import", router.Import)
	auth.POST("/exec", router.Exec)
	r.Run(":" + port)
}

func login(c *gin.Context) {
	var user utils.User
	err := c.ShouldBind(&user)
	if err != nil {
		fmt.Println(err)
	}
	if user.Username == username && user.Password == password {
		token, err := utils.GenToken(user)
		if err != nil {
			fmt.Println(err)
		}
		c.JSON(200, common.OK(token))
	} else {
		c.JSON(200, common.Error(201, "用户名或密码不正确"))
	}
}

func initRouter(r *gin.Engine) {
	r.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"*"},
		AllowMethods:     []string{"GET", "POST", "DELETE", "PUT", "HEAD", "OPTIONS"},
		AllowHeaders:     []string{"Origin", "authorization", "content-type"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: true,
		MaxAge:           12 * time.Hour,
	}))

	r.GET("/", func(c *gin.Context) {
		c.Status(http.StatusFound)
		c.Writer.Header().Set("Location", "./dashbord/")
	})

	front, err := fs.Sub(embedFrontend, "web/dist")
	if err != nil {
		fmt.Println(err)
	}

	HTTPCacheForUI(r)
	r.StaticFS("/dashbord/", http.FS(front))

	r.NoRoute(func(c *gin.Context) {
		fmt.Println("=========>c.Request.RequestURI", c.Request.RequestURI)
		if strings.HasPrefix(c.Request.RequestURI, "/dashbord/") {
			path := strings.TrimPrefix(c.Request.RequestURI, "/dashbord/")
			locationPath := strings.Repeat("../", strings.Count(path, "/"))
			c.Status(http.StatusFound)
			c.Writer.Header().Set("Location", "./"+locationPath)
		}
	})
}

func HTTPCacheForUI(app *gin.Engine) {
	app.Use(func(c *gin.Context) {
		if c.Request.Method == "GET" || c.Request.Method == "HEAD" {
			if strings.Contains(c.Request.RequestURI, "/dashbord/static/") {
				c.Writer.Header().Set("cache-control", "public, max-age=2592000")
				c.Writer.Header().Set("expires", time.Now().Add(time.Hour*24*30).Format(time.RFC1123))
				if strings.Contains(c.Request.RequestURI, ".js") {
					c.Writer.Header().Set("content-type", "application/javascript")
				}
				if strings.Contains(c.Request.RequestURI, ".css") {
					c.Writer.Header().Set("content-type", "text/css; charset=utf-8")
				}
			}
		}
		c.Next()
	})
}

func Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method
		origin := c.Request.Header.Get("Origin") //请求头部
		if origin != "" {
			//接收客户端发送的origin （重要！）
			//Access-Control-Allow-Origin是必须的,他的值要么是请求Origin字段的值,要么是一个*, 表示接受任意域名的请求
			c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
			//服务器支持的所有跨域请求的方法
			c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
			//允许跨域设置可以返回其他子段，可以自定义字段
			//该字段可选。CORS请求时，XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段：Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。
			//如果想拿到其他字段，就必须在Access-Control-Expose-Headers里面指定。上面的例子指定，getResponseHeader('FooBar')可以返回FooBar字段的值。
			c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token, session")
			// 允许浏览器（客户端）可以解析的头部 （重要）
			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
			//设置缓存时间
			//该字段可选，用来指定本次预检请求的有效期，单位为秒。有效期是20天（1728000秒），即允许缓存该条回应1728000秒（即20天），在此期间，不用发出另一条预检请求。
			c.Header("Access-Control-Max-Age", "172800")
			//允许客户端传递校验信息比如 cookie (重要)
			c.Header("Access-Control-Allow-Credentials", "true")
		}
		//允许类型校验
		if method == "OPTIONS" {
			c.JSON(http.StatusOK, "ok!")
		}
		defer func() {
			if err := recover(); err != nil {
				fmt.Println(err)
			}
		}()
		c.Next()
	}
}

func argsFilter() gin.HandlerFunc {
	return func(c *gin.Context) {
		table := c.DefaultQuery("table", "")
		if len(table) > 0 && !verifyArgs.MatchString(table) {
			c.JSON(200, common.Error(200, "参数错误"))
			return
		}
		chain := c.DefaultQuery("chain", "")
		if len(chain) > 0 && !verifyArgs.MatchString(chain) {
			c.JSON(200, common.Error(200, "参数错误"))
			return
		}
		c.Next()
	}
}

func auth() gin.HandlerFunc {
	return func(c *gin.Context) {
		token := c.PostForm("token")
		if token == "" {
			token = c.DefaultQuery("token", "")
		}
		if token == "" {
			token = c.Request.Header.Get("token")
		}
		if token == "" {
			c.Abort()
			c.JSON(200, common.Error(204, "非法访问"))
			return
		}
		_, err := utils.GetTokenUser(token)
		if err != nil {
			//token过期
			c.Abort()
			c.JSON(200, common.Error(204, "token已过期"))
			return
		} else {
			c.Next()
		}
	}
}
